module.exports = function( grunt ) {

	"use strict";

	var isConnectTestRunning,
		rdefineEnd = /\}\);[^}\w]*$/,
		pkg = grunt.file.readJSON( "package.json" );

	function camelCase( input ) {
		return input.toLowerCase().replace( /[-/](.)/g, function( match, group1 ) {
			return group1.toUpperCase();
		});
	}

	function mountFolder( connect, path ) {
		return connect.static( require( "path" ).resolve( path ) );
	}

	function replaceConsts( content ) {
		return content

			// Replace Version
			.replace( /@VERSION/g, pkg.version )

			// Replace Date yyyy-mm-ddThh:mmZ
			.replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
	}

	grunt.initConfig({
		pkg: pkg,
		authors: {
			order: "count"
		},
		commitplease: {
			last50: {
				options: {
					committish: "-n 50"
				}
			}
		},
		connect: {
			options: {
				port: 9001,
				hostname: "localhost"
			},
			test: {
				options: {
					middleware: function( connect ) {
						return [
							mountFolder( connect, "." ),
							mountFolder( connect, "test" )
						];
					}
				}
			},
			keepalive: {
				options: {
					keepalive: true,
					middleware: function( connect ) {
						return [
							mountFolder( connect, "." )
						];
					}
				}
			}
		},
		jshint: {
			source: {
				src: [ "src/**/*.js", "!src/build/**" ],
				options: {
					jshintrc: "src/.jshintrc"
				}
			},
			grunt: {
				src: [ "Gruntfile.js" ],
				options: {
					jshintrc: ".jshintrc"
				}
			},
			test: {
				src: [ "test/*.js", "test/functional/**/*.js", "test/unit/**/*.js",
					"test/compiler/**/*.js", "!test/config.js", "!test/compiler/_compiled/**" ],
				options: {
					jshintrc: "test/.jshintrc"
				}
			},
			dist: {
				src: [ "dist/globalize*.js", "dist/globalize/*.js" ],
				options: {
					jshintrc: "src/.dist-jshintrc"
				}
			}
		},
		jscs: {
			source: [ "src/**/*.js", "!src/build/**" ],
			grunt: "Gruntfile.js",
			test: [ "test/*.js", "test/functional/**/*.js", "test/unit/**/*.js",
				"test/compiler/**/*.js", "!test/compiler/_compiled/**" ],
			dist: [ "dist/globalize*.js", "dist/globalize/*.js" ]
		},
		mochaTest: {
			test: {
				options: {
					reporter: "spec"
				},
				src: [ "test/compiler/*.js" ]
			}
		},
		qunit: {
			functional: {
				options: {
					urls: [

						// Use es5-shim here due to .bind(), which is not present on phantomjs v1.9.
						// But, it should be on v2.x.
						"http://localhost:<%= connect.options.port %>/functional-es5-shim.html"
					]
				}
			},
			unit: {
				options: {
					urls: [ "http://localhost:<%= connect.options.port %>/unit.html" ]
				}
			}
		},
		requirejs: {
			options: {
				dir: "dist/.build",
				appDir: "src",
				baseUrl: ".",
				optimize: "none",
				paths: {
					cldr: "../external/cldrjs/dist/cldr",
					"make-plural": "../external/make-plural/make-plural",
					messageformat: "../external/messageformat/messageformat",
					"zoned-date-time": "../node_modules/zoned-date-time/src/zoned-date-time"
				},
				shim: {
					"zoned-date-time": {
						exports: "ZonedDateTime"
					}
				},
				skipSemiColonInsertion: true,
				skipModuleInsertion: true,

				// Strip all definitions generated by requirejs.
				// Convert content as follows:
				// a) "Single return" means the module only contains a return statement that is
				//    converted to a var declaration.
				// b) "Module" means the define wrappers are removed, but content is untouched.
				//    Only for root id's (the ones in src, not in src's subpaths). Note there's no
				//    conditional code checking for this type.
				onBuildWrite: function( id, path, contents ) {
					var messageformat,
						name = camelCase( id.replace( /util\/|common\//, "" ) );

					// MakePlural
					if ( ( /make-plural/ ).test( id ) ) {
						return contents

							// Remove browserify wrappers.
							.replace( /^\(function\(f\){if\(typeof exports==="object"&&type.*/, "" )
							.replace( /\},\{\}\]\},\{\},\[1\]\)\(1\)[\s\S]*?$/, "" )

							// Remove browserify exports.
							.replace( /Object.defineProperty\(exports[\s\S]*?\n}\);/, "" )
							.replace( "exports['default'] = MakePlural;", "" )
							.replace( "module.exports = exports['default'];", "" )

							// Remove self-tests.
							.replace( /var Tests =[\s\S]*?\n}\)\(\);/, "" )
							.replace( "this.tests = new Tests(this);", "" )
							.replace( /this.fn.test =[\s\S]*?bind\(this\);/, "" )
							.replace( "this.tests.add(type, cat, examples);", "" )

							// Remove load method.
							.replace( /load: \{[\s\S]*?\n        \}/, "" )

							// Replace its wrapper into var assignment.
							.replace( /\(function \(global\) \{/, [
								"var MakePlural;",
								"/* jshint ignore:start */",
								"MakePlural = (function() {"
							].join( "\n" ) )
							.replace( /if \(\(typeof module !== 'undefined'[\s\S]*/, [
								"return MakePlural;",
								"}());",
								"/* jshint ignore:end */"
							].join( "\n" ) )

							// Wrap everything into a var assignment.
							.replace( /^/, [
								"var MakePlural;",
								"/* jshint ignore:start */",
								"MakePlural = (function() {"
							].join( "\n" ) )
							.replace( /$/, [
								"return MakePlural;",
								"}());",
								"/* jshint ignore:end */"
							].join( "\n" ) );

					// messageformat
					} else if ( ( /messageformat/ ).test( id ) ) {
						return contents

							// Remove browserify wrappers.
							.replace( /^\(function\(f\)\{if\(typeof exports==="object"&&type.*/, "" )
							.replace( "},{}],2:[function(require,module,exports){", "" )
							.replace( /\},\{"\.\/messageformat-parser":1,"make-plural\/plural.*/, "" )
							.replace( /\},\{\}\]\},\{\},\[2\]\)\(2\)[\s\S]*?$/, "" )

							// Set `MessageFormat.plurals` and remove `make-plural/plurals`
							// completely. This is populated by Globalize on demand.
							.replace( /var _cp = \[[\s\S]*?$/, "" )
							.replace(
								"MessageFormat.plurals = require('make-plural/plurals')",
								"MessageFormat.plurals = {}"
							)

							// Set `MessageFormat._parse`
							.replace(
								"MessageFormat._parse = require('./messageformat-parser').parse;",
								""
							)
							.replace( /module\.exports = \(function\(\) \{([\s\S]*?)\n\}\)\(\);/, [
								"MessageFormat._parse = (function() {",
								"$1",
								"}()).parse;"
							].join( "\n" ) )

							// Remove unused code.
							.replace( /if \(!pluralFunc\) \{\n[\s\S]*?\n  \}/, "" )
							.replace( /if \(!locale\) \{\n[\s\S]*?  \}\n/, "this.lc = [locale];" )
							.replace( /(MessageFormat\.formatters) = \{[\s\S]*?\n\};/, "$1 = {};" )
							.replace( /MessageFormat\.prototype\.setIntlSupport[\s\S]*?\n\};/, "" )

							// Wrap everything into a var assignment.
							.replace( "module.exports = MessageFormat;", "" )
							.replace( /^/, [
								"var MessageFormat;",
								"/* jshint ignore:start */",
								"MessageFormat = (function() {"
							].join( "\n" ) )
							.replace( /$/, [
								"return MessageFormat;",
								"}());",
								"/* jshint ignore:end */"
							].join( "\n" ) );

					// message-runtime
					} else if ( ( /message-runtime/ ).test( id ) ) {
						messageformat = require( "./external/messageformat/messageformat" );
						delete messageformat.prototype.runtime.fmt;
						delete messageformat.prototype.runtime.pluralFuncs;
						contents = contents.replace( "Globalize._messageFormat = {};", [
							"/* jshint ignore:start */",
							"Globalize._messageFormat = (function() {",
							messageformat.prototype.runtime.toString(),
							"return {number: number, plural: plural, select: select};",
							"}());",
							"/* jshint ignore:end */"
						].join( "\n" ) );

					// ZonedDateTime
					} else if ( ( /zoned-date-time/ ).test( id ) ) {
						contents = contents.replace(
							"module.exports = ZonedDateTime;",
							"return ZonedDateTime;"
						);
						contents = "var ZonedDateTime = (function() {\n" + contents + "}());";
					}

					// 1, and 2: Remove define() wrap.
					// 3: Remove empty define()'s.
					contents = contents
						.replace( /define\([^{]*?\{/, "" ) /* 1 */
						.replace( rdefineEnd, "" ) /* 2 */
						.replace( /define\(\[[^\]]+\]\)[\W\n]+$/, "" ); /* 3 */

					// Type b (not as simple as a single return)
					if ( [ "expand-pattern/augment-format" ].indexOf( id ) !== -1 ) {
						contents = "var " + name + " = (function() {" + contents + "}());";

					// Type a (single return)
					} else if ( ( /\// ).test( id ) ) {
						contents = contents
							.replace( /\nreturn/, "\nvar " + name + " =" );
					}

					return contents;
				}
			},
			bundle: {
				options: {
					modules: [
						{
							name: "globalize",
							include: [ "core" ],
							exclude: [ "cldr", "cldr/event" ],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-core.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.currency",
							include: [ "currency" ],
							exclude: [
								"cldr",
								"cldr/event",
								"cldr/supplemental",
								"./core",
								"./number"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-currency.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.date",
							include: [ "date" ],
							exclude: [
								"cldr",
								"cldr/event",
								"cldr/supplemental",
								"./core",
								"./number"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-date.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.message",
							include: [ "message" ],
							exclude: [ "cldr", "./core" ],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-message.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.number",
							include: [ "number" ],
							exclude: [
								"cldr",
								"cldr/event",
								"cldr/supplemental",
								"./core"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-number.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.plural",
							include: [ "plural" ],
							exclude: [
								"cldr",
								"cldr/event",
								"cldr/supplemental",
								"./core"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-plural.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.relative-time",
							include: [ "relative-time" ],
							exclude: [
								"cldr",
								"cldr/event",
								"cldr/supplemental",
								"./core",
								"./number",
								"./plural"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-relative-time.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.unit",
							include: [ "unit" ],
							exclude: [
								"cldr",
								"./core",
								"./number",
								"./plural"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-unit.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize-runtime",
							include: [ "core-runtime" ],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-core-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.currency-runtime",
							include: [ "currency-runtime" ],
							exclude: [
								"./core-runtime",
								"./number-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-currency-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.date-runtime",
							include: [ "date-runtime" ],
							exclude: [
								"./core-runtime",
								"./number-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-date-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.message-runtime",
							include: [ "message-runtime" ],
							exclude: [ "./core-runtime" ],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-message-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.number-runtime",
							include: [ "number-runtime" ],
							exclude: [
								"./core-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-number-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.plural-runtime",
							include: [ "plural-runtime" ],
							exclude: [
								"./core-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-plural-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.relative-time-runtime",
							include: [ "relative-time-runtime" ],
							exclude: [
								"./core-runtime",
								"./number-runtime",
								"./plural-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-relative-time-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						},
						{
							name: "globalize.unit-runtime",
							include: [ "unit-runtime" ],
							exclude: [
								"./core-runtime",
								"./number-runtime",
								"./plural-runtime"
							],
							create: true,
							override: {
								wrap: {
									startFile: "src/build/intro-unit-runtime.js",
									endFile: "src/build/outro.js"
								}
							}
						}
					]
				}
			}
		},
		watch: {
			files: [ "src/*.js", "test/functional/**/*.js", "test/unit/**/*.js", "test/*.html" ],
			tasks: [ "default" ]
		},
		copy: {
			options: {
				processContent: function( content ) {

					// Remove leftover define created during rjs build
					content = content.replace( /define\(".*/, "" );

					// Embed VERSION and DATE
					return replaceConsts( content );
				}
			},
			coreAndRuntime: {
				expand: true,
				cwd: "dist/.build/",
				src: [ "globalize.js", "globalize-runtime.js" ],
				dest: "dist/"
			},
			modules: {
				expand: true,
				cwd: "dist/.build/",
				src: [ "globalize*.js", "!globalize.js", "!*runtime*.js" ],
				dest: "dist/globalize",
				rename: function( dest, src ) {
					return require( "path" ).join( dest, src.replace( /globalize\./, "" ) );
				}
			},
			runtimeModules: {
				expand: true,
				cwd: "dist/.build/",
				src: [ "globalize.*runtime.js" ],
				dest: "dist/globalize-runtime",
				rename: function( dest, src ) {
					return require( "path" ).join( dest, src.replace( /(globalize\.|-runtime)/g, "" ) );
				}
			},
			allInOneNode: {
				src: "src/build/node-main.js",
				dest: "dist/node-main.js"
			}
		},
		uglify: {
			options: {
				banner: replaceConsts( grunt.file.read( "src/build/intro.min.js" ) )
			},
			dist: {
				files: {
					"tmp/globalize.min.js": [ "dist/globalize.js" ],
					"tmp/globalize/currency.min.js": [ "dist/globalize/currency.js" ],
					"tmp/globalize/date.min.js": [ "dist/globalize/date.js" ],
					"tmp/globalize/number.min.js": [ "dist/globalize/number.js" ],
					"tmp/globalize/plural.min.js": [ "dist/globalize/plural.js" ],
					"tmp/globalize/message.min.js": [ "dist/globalize/message.js" ],
					"tmp/globalize/relative-time.min.js": [ "dist/globalize/relative-time.js" ],
					"tmp/globalize/unit.min.js": [ "dist/globalize/unit.js" ],

					"tmp/globalize-runtime.min.js": [ "dist/globalize-runtime.js" ],
					"tmp/globalize-runtime/currency.min.js": [
						"dist/globalize-runtime/currency.js"
					],
					"tmp/globalize-runtime/date.min.js": [ "dist/globalize-runtime/date.js" ],
					"tmp/globalize-runtime/message.min.js": [ "dist/globalize-runtime/message.js" ],
					"tmp/globalize-runtime/number.min.js": [ "dist/globalize-runtime/number.js" ],
					"tmp/globalize-runtime/plural.min.js": [ "dist/globalize-runtime/plural.js" ],
					"tmp/globalize-runtime/relative-time.min.js": [
						"dist/globalize-runtime/relative-time.js"
					],
					"tmp/globalize-runtime/unit.min.js": [ "dist/globalize-runtime/unit.js" ]
				}
			}
		},

		// TODO figure out how to specify exceptions for externals
		"compare_size": {
			files: [
				"tmp/globalize.min.js",
				"tmp/globalize/*min.js",
				"tmp/globalize-runtime.min.js",
				"tmp/globalize-runtime/*min.js"
			],
			options: {
				compress: {
					gz: function( fileContents ) {
						return require( "gzip-js" ).zip( fileContents, {}).length;
					}
				}
			}
		},
		clean: {
			dist: [
				"dist"
			]
		},
		checkDependencies: {
			bower: {
				options: {
					packageManager: "bower"
				}
			},
			npm: {
				options: {
					packageManager: "npm"
				}
			}
		}
	});

	require( "matchdep" ).filterDev( "grunt-*" ).forEach( grunt.loadNpmTasks );

	grunt.registerTask( "test", function() {
		var args = [].slice.call( arguments );
		if ( !isConnectTestRunning ) {
			grunt.task.run( "checkDependencies" );
			grunt.task.run( "connect:test" );
			isConnectTestRunning = true;
		}
		grunt.task.run( [ "qunit" ].concat( args ).join( ":" ) );
	});

	// Default task.
	grunt.registerTask( "default", [
		"jshint:grunt",
		"jshint:source",
		"jshint:test",
		"jscs:grunt",
		"jscs:source",

		// TODO fix issues, enable
		//"jscs:test",
		"test:unit",
		"clean",
		"requirejs",
		"copy",
		"jshint:dist",

		// TODO fix issues, enable
		// "jscs:dist",
		"test:functional",
		"mochaTest",
		"uglify",
		"compare_size",
		"commitplease"
	]);

};
